/*
 * LTE Game Engine
 * Copyright (C) 2006-2008 SiberianSTAR <haxormail@gmail.com>
 * http://www.ltestudios.com
 *  
 * The LTE Game Engine is based on Irrlicht 1.0
 * Irrlicht Engine is Copyright (C) 2002-2006 Nikolaus Gebhardt
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
 
//
// This file was written by Saurav Mohapatra and modified by Nikolaus Gebhardt.
// See CCSMLoader.h for details.

#include "CCSMLoader.h"
#include "os.h"
#include "IFileSystem.h"
#include "IReadFile.h"
#include "ISceneManager.h"
#include "IStringParameters.h"
#include "SMesh.h"
#include "IVideoDriver.h"
#include <string.h>
#include "SAnimatedMesh.h"

namespace engine
{
namespace scene
{
//
//	the CSM data types
//
struct color_rgb_t
{
	s32 red;
	s32 green;
	s32 blue;

	void clear()
	{
		red = 0;
		green = 0;
		blue = 0;
	}
};

class BinaryFileReader;

//
//	The file header
//
class Header
{
public:

static const s32 VERSION_4;
static const s32 VERSION_4_1;

Header(){
	clear();
}
virtual ~Header(){
	clear();
}

const s32 getVersion() const {
	return version;
}
void clear(){
	version = 0;
}
void load(BinaryFileReader* pReader);

private:

s32 version;
};


//
//	The groups
//
class Group
{
public:

Group(){
	clear();
}
virtual ~Group(){
	clear();
}

void clear();
void load(BinaryFileReader* pReader);

s32 getFlags() const {
	return flags;
}
s32 getParentGroupID() const {
	return parentGroup;
}
const core::stringc& getProperties() const {
	return props;
}
const color_rgb_t* getColor() const {
	return &color;
}

private:

s32 flags;
s32 parentGroup;
core::stringc props;
color_rgb_t color;
};


//
//	The visgroups
//
class VisGroup
{
public:

VisGroup(){
	clear();
}
virtual ~VisGroup(){
	clear();
}
void clear();
void load(BinaryFileReader* pReader);

s32 getFlags() const {
	return flags;
}
const core::stringc& getName() const {
	return name;
}
const color_rgb_t* getColor() const {
	return &color;
}

private:

core::stringc name;
s32 flags;
color_rgb_t color;
};


//
//	Lightmaps
//
class LightMap
{
public:

LightMap() : pixelData(0){
	clear();
}
virtual ~LightMap(){
	clear();
}
void clear();
void load(BinaryFileReader* pReader);
const s32 getWidth() const {
	return width;
}
const s32 getHeight() const {
	return height;
}
const s32* getPixelData() const {
	return pixelData;
}

private:

s32 width;
s32 height;
s32* pixelData;
};

struct Triangle
{
	s32 a,b,c;
};


struct Line
{
	s32 a,b;
};


class Vertex
{
public:

Vertex(){
	clear();
}
virtual ~Vertex(){
	clear();
}
void clear();
void load(BinaryFileReader* pReader);

const core::vector3df* getPosition() const {
	return &position;
}
const core::vector3df* getNormal() const {
	return &normal;
}
const color_rgb_t* getColor() const {
	return &color;
}
const core::vector3df* getTextureCoordinates() const {
	return &texCoords;
}
const core::vector3df* getLightMapCoordinates() const {
	return &lmapCoords;
}

private:

core::vector3df position;
core::vector3df normal;
color_rgb_t color;
core::vector3df texCoords;
core::vector3df lmapCoords;
};


class Surface
{
public:

Surface() {
	clear();
}
virtual ~Surface(){
	clear();
}

void clear();
void load(BinaryFileReader *pReader);

const s32 getFlags() const {
	return flags;
}
const core::stringc& getTextureName() const {
	return textureName;
}
const s32 getLightMapId() const {
	return lightMapId;
}
const core::vector2df* getUVOffset() const {
	return &uvOffset;
}
const core::vector2df* getUVScale() const {
	return &uvScale;
}
const f32 getUVRotation() const {
	return uvRotation;
}

const s32 getVertexCount() const {
	return vertices.size();
}
const Vertex* getVertexAt(const s32 index) const {
	return vertices[index];
}

const s32 getTriangleCount() const {
	return triangles.size();
}
const Triangle& getTriangleAt(const s32 index) const {
	return triangles[index];
}

private:

s32 flags;
core::stringc textureName;
s32 lightMapId;
core::vector2df uvOffset;
core::vector2df uvScale;
f32 uvRotation;
core::array<Vertex*> vertices;
core::array<Triangle> triangles;
core::array<Line> lines;
};

class Mesh
{
public:

Mesh(){
	clear();
}
virtual ~Mesh(){
	clear();
}

void clear();
void load(BinaryFileReader* pReader, bool bReadVisGroups);

const s32 getFlags() const {
	return flags;
}
const s32 getGroupID() const {
	return groupId;
}
const core::stringc& getProperties() const {
	return props;
}
const color_rgb_t* getColor() const {
	return &color;
}
const core::vector3df* getPosition() const {
	return &position;
}
const s32 getVisgroupID() const {
	return visgroupId;
}
const s32 getSurfaceCount() const {
	return surfaces.size();
}
const Surface* getSurfaceAt(const s32 index) const {
	return surfaces[index];
}

private:

s32 flags;
s32 groupId;
core::stringc props;
color_rgb_t color;
core::vector3df position;
s32 visgroupId;

core::array<Surface*> surfaces;
};

class Entity
{
public:

Entity() {
	clear();
}
virtual ~Entity() {
	clear();
}

void clear();
void load(BinaryFileReader* pReader);
const s32 getVisgroupID() const {
	return visgroupId;
}
const s32 getGroupID() const {
	return groupId;
}
const core::stringc& getProperties() const {
	return props;
}
const core::vector3df* getPosition() const {
	return &position;
}

private:

s32 visgroupId;
s32 groupId;
core::stringc props;
core::vector3df position;
};


class CameraData
{
public:

CameraData(){
	clear();
}
virtual ~CameraData(){
	clear();
}

void clear();
void load(BinaryFileReader* pReader);

const core::vector3df* getPosition(){
	return &position;
}
const f32 getPitch(){
	return pitch;
}
const f32 getYaw(){
	return yaw;
}

private:

core::vector3df position;
f32 pitch;
f32 yaw;
};

//
//	A CSM File
//
class CSMFile
{
public:

CSMFile(){
	clear();
}
virtual ~CSMFile(){
	clear();
}
void clear();
void load(BinaryFileReader* pReader);

const Header* getHeader() const {
	return &header;
}

const s32 getGroupCount() const {
	return groups.size();
}
const Group* getGroupAt(const s32 index) const {
	return groups[index];
}

const s32 getVisGroupCount() const {
	return visgroups.size();
}
const VisGroup* getVisGroupAt(const s32 index) const {
	return visgroups[index];
}

const s32 getLightMapCount() const {
	return lightmaps.size();
}
const LightMap* getLightMapAt(const s32 index) const {
	return lightmaps[index];
}

const s32 getMeshCount() const {
	return meshes.size();
}
const Mesh* getMeshAt(const s32 index) const {
	return meshes[index];
}

const s32 getEntityCount() const {
	return entities.size();
}
const Entity* getEntityAt(const s32 index) const {
	return entities[index];
}

const CameraData* getCameraData() const {
	return &cameraData;
}

private:

Header header;
core::array<Group*> groups;
core::array<VisGroup*> visgroups;
core::array<LightMap*> lightmaps;
core::array<Mesh*> meshes;
core::array<Entity*> entities;
CameraData cameraData;
};


//
//	A Binary File Reader
//
class BinaryFileReader
{
public:

BinaryFileReader(io::IReadFile* pFile, bool closeWhenDone=true);
virtual ~BinaryFileReader();

virtual s32 readBuffer(void* buffer, s32 len);

s32 readLong();
f32 readFloat();
u8  readByte();

core::stringc readString();
void readVec3f(core::vector3df* v);
void readVec2f(core::vector2df* v);
void readColorRGB(color_rgb_t* color);

private:

io::IReadFile *file;
bool autoClose;

};

CCSMLoader::CCSMLoader(scene::ISceneManager* manager, io::IFileSystem* fs)
	: FileSystem(fs), SceneManager(manager)
{
}

CCSMLoader::~CCSMLoader()
{
}

//! returns true if the file maybe is able to be loaded by this class
//! based on the file extension (e.g. ".bsp")
bool CCSMLoader::isALoadableFileExtension(const c8* fileName)
{
	return strstr(fileName, ".csm")!=0;
}

//! creates/loads an animated mesh from the file.
IAnimatedMesh* CCSMLoader::createMesh(engine::io::IReadFile* file)
{
	file->grab();         // originally, this loader created the file on its own.

	scene::IMesh* m = createCSMMesh(file);

	if (!m)
		return 0;

	SAnimatedMesh* am = new SAnimatedMesh();
	am->Type = EAMT_CSM;
	am->addMesh(m);
	m->drop();

	am->recalculateBoundingBox();
	return am;
}

scene::IMesh* CCSMLoader::createCSMMesh(engine::io::IReadFile* file)
{
	if (!file)
		return 0;

	if(file)
	{
		BinaryFileReader reader(file, true);
		CSMFile csmFile;
		csmFile.load(&reader);

		scene::IMesh* pMesh = createengineMesh(&csmFile,
		                                       SceneManager->getParameters()->getParameter(CSM_TEXTURE_PATH),
		                                       file->getFileName());
		return pMesh;
	}

	return 0;
}

scene::IMesh* CCSMLoader::createengineMesh(const CSMFile* csmFile,
                                           core::stringc textureRoot, const c8* lmprefix)
{
	scene::SMesh *pMesh = new scene::SMesh();
	video::IVideoDriver* driver = SceneManager->getVideoDriver();

	for(s32 l = 0; l<csmFile->getLightMapCount(); l++)
	{
		const LightMap* lmap = csmFile->getLightMapAt(l);

		core::stringc lmapName = lmprefix;
		lmapName += "LMAP_";
		lmapName += (int)(l+1);
		os::Printer::log("CCSMLoader loading light map", lmapName.c_str());

		video::IImage* lmapImg = driver->createImageFromData(
		        video::ECF_A8R8G8B8,
		        core::dimension2d<s32>(lmap->getWidth(),lmap->getHeight()),
		        (void *)(lmap->getPixelData()));

		driver->addTexture(lmapName.c_str(), lmapImg);
		lmapImg->drop();

	}

	for(s32 m = 0; m<csmFile->getMeshCount(); m++)
	{
		const Mesh* mshPtr = csmFile->getMeshAt(m);

		for(s32 s = 0; s < mshPtr->getSurfaceCount(); s++)
		{
			const Surface* surface = mshPtr->getSurfaceAt(s);

			core::stringc texName = textureRoot;
			texName+= "/";
			texName+= surface->getTextureName();

			video::ITexture* texture = driver->getTexture(texName.c_str());
			scene::SMeshBufferLightMap *buffer = new scene::SMeshBufferLightMap();

			//material
			core::stringc lmapName = lmprefix;
			lmapName += "LMAP_";
			lmapName += (int)surface->getLightMapId();

			buffer->Material.Texture1 = texture;
			buffer->Material.Texture2 = driver->getTexture(lmapName.c_str());
			buffer->Material.Lighting = false;
			buffer->Material.MaterialType = video::EMT_LIGHTMAP_M4;

			for(s32 v = 0; v < surface->getVertexCount(); v++)
			{
				const Vertex *vtxPtr = surface->getVertexAt(v);
				video::S3DVertex2TCoords vtx;
				vtx.Pos = *(vtxPtr->getPosition());
				vtx.Normal = *(vtxPtr->getPosition());
				vtx.Color.set(255,vtxPtr->getColor()->red,vtxPtr->getColor()->green,vtxPtr->getColor()->blue);
				vtx.TCoords.set(vtxPtr->getTextureCoordinates()->X,vtxPtr->getTextureCoordinates()->Y);
//					vtx.TCoords2.set(vtxPtr->getLightMapCoordinates()->X,0 - vtxPtr->getLightMapCoordinates()->Y);

				buffer->Vertices.push_back(vtx);
			}

			for(s32 t = 0; t < surface->getTriangleCount(); t++)
			{
				const Triangle& tri = surface->getTriangleAt(t);
				buffer->Indices.push_back(tri.a);
				buffer->Indices.push_back(tri.c);
				buffer->Indices.push_back(tri.b);
			}

			buffer->recalculateBoundingBox();
			pMesh->addMeshBuffer(buffer);
			buffer->drop();
		}
	}

	pMesh->recalculateBoundingBox();
	return pMesh;
}

const s32 Header::VERSION_4 = 4;
const s32 Header::VERSION_4_1 = 5;

void Header::load(BinaryFileReader* pReader)
{
	version = pReader->readLong();
}

void Group::clear()
{
	color.clear();
	flags = 0;
	parentGroup = 0;
	props = "";
}

void Group::load(BinaryFileReader* pReader)
{
	flags = pReader->readLong();
	parentGroup = pReader->readLong();
	props = pReader->readString();
	pReader->readColorRGB(&color);
}

void VisGroup::clear()
{
	color.clear();
	flags = 0;
	name = "";

}

void VisGroup::load(BinaryFileReader* pReader)
{
	name = pReader->readString();
	flags = pReader->readLong();
	pReader->readColorRGB(&color);
}

void LightMap::clear()
{
	if(pixelData)
	{
		delete[] pixelData;
		pixelData = 0;
	}
	width = height = 0;
}

void LightMap::load(BinaryFileReader* pReader)
{
	width = pReader->readLong();
	height = pReader->readLong();
	pixelData = new s32[width * height];
	pReader->readBuffer(pixelData, width * height * sizeof(s32));
}

void Mesh::clear()
{
	flags = 0;
	groupId = 0;
	visgroupId = 0;
	props = "";
	color.clear();
	position.set(0,0,0);

	for(u32 s = 0; s < surfaces.size(); s++)
	{
		if(surfaces[s])
		{
			delete surfaces[s];
		}
	}
	surfaces.clear();
}

void Mesh::load(BinaryFileReader* pReader, bool bReadVisGroups)
{
	flags = pReader->readLong();
	groupId = pReader->readLong();
	props = pReader->readString();
	pReader->readColorRGB(&color);
	pReader->readVec3f(&position);
	if(bReadVisGroups)
		visgroupId = pReader->readLong();
	else
		visgroupId = 0;

	s32 count = pReader->readLong();

	for(s32 i = 0; i < count; i++)
	{
		Surface* surf = new Surface();
		surf->load(pReader);
		surfaces.push_back(surf);
	}
}

void Surface::clear()
{
	flags = 0;
	lightMapId = 0;
	textureName = 0;
	uvOffset.set(0,0);
	uvScale.set(0,0);
	uvRotation = 0;
	triangles.clear();
	lines.clear();

	for(u32 v = 0; v < vertices.size(); v++)
		delete vertices[v];

	vertices.clear();
}

void Surface::load(BinaryFileReader* pReader)
{

	flags = pReader->readLong();
	textureName = pReader->readString();

	lightMapId = pReader->readLong();
	pReader->readVec2f(&uvOffset);
	pReader->readVec2f(&uvScale);
	uvRotation = pReader->readFloat();
	s32 vtxCount = pReader->readLong();
	s32 triCount = pReader->readLong();
	s32 lineCount = pReader->readLong();

	for(s32 v = 0; v < vtxCount; v++)
	{
		Vertex *vtx = new Vertex();
		vtx->load(pReader);
		vertices.push_back(vtx);
	}

	for(s32 t = 0; t < triCount; t++)
	{
		Triangle tri;
		pReader->readBuffer(&tri, sizeof(tri));
		triangles.push_back(tri);
	}

	for(s32 l = 0; l < lineCount; l++)
	{
		Line line;
		pReader->readBuffer(&line,sizeof(line));
		lines.push_back(line);

	}

}

void Vertex::clear()
{
	position.set(0,0,0);
	normal.set(0,0,0);
	color.clear();
	texCoords.set(0,0,0);
	lmapCoords.set(0,0,0);

}

void Vertex::load(BinaryFileReader* pReader)
{
	pReader->readVec3f(&position);
	pReader->readVec3f(&normal);
	pReader->readColorRGB(&color);
	pReader->readVec3f(&texCoords);
	pReader->readVec3f(&lmapCoords);
}

void Entity::clear()
{
	visgroupId = groupId = 0;
	props = "";
	position.set(0,0,0);
}

void Entity::load(BinaryFileReader* pReader)
{
	visgroupId = pReader->readLong();
	groupId = pReader->readLong();
	props = pReader->readString();
	pReader->readVec3f(&position);
}

void CameraData::clear()
{
	position.set(0,0,0);
	pitch = 0;
	yaw = 0;
}

void CameraData::load(BinaryFileReader* pReader)
{
	pReader->readVec3f(&position);
	pitch = pReader->readFloat();
	yaw = pReader->readFloat();
}

void CSMFile::clear()
{
	header.clear();
	cameraData.clear();

	u32 x =0;
	for( x= 0; x < groups.size(); x++)
		delete groups[x];

	groups.clear();

	for(x= 0; x < visgroups.size(); x++)
		delete visgroups[x];

	visgroups.clear();

	for(x= 0; x < lightmaps.size(); x++)
		delete lightmaps[x];

	lightmaps.clear();

	for(x= 0; x < meshes.size(); x++)
		delete meshes[x];

	meshes.clear();

	for(x= 0; x < entities.size(); x++)
		delete entities[x];

	entities.clear();
}

void CSMFile::load(BinaryFileReader* pReader)
{
	clear();

	header.load(pReader);

	//groups
	{

		s32 count = pReader->readLong();

		for (s32 i = 0; i < count; i++)
		{
			Group* grp = new Group();
			grp->load(pReader);
			groups.push_back(grp);
		}
	}
	bool bHasVGroups = (header.getVersion() == Header::VERSION_4_1);

	if (bHasVGroups)
	{
		//visgroups
		s32 count = pReader->readLong();

		for (s32 i = 0; i < count; i++)
		{
			VisGroup* grp = new VisGroup();
			grp->load(pReader);
			visgroups.push_back(grp);
		}
	}

	//lightmaps
	{

		s32 count = pReader->readLong();

		for(s32 i = 0; i < count; i++)
		{
			LightMap* grp = new LightMap();
			grp->load(pReader);
			lightmaps.push_back(grp);
		}
	}

	//meshes
	{
		s32 count = pReader->readLong();

		for(s32 i = 0; i < count; i++)
		{
			Mesh* grp = new Mesh();
			grp->load(pReader,bHasVGroups);
			meshes.push_back(grp);
		}
	}

	//entities
	{
		s32 count = pReader->readLong();

		for(s32 i = 0; i < count; i++)
		{
			Entity* grp = new Entity();
			grp->load(pReader);
			entities.push_back(grp);
		}
	}

	//camera data
	cameraData.load(pReader);


}

BinaryFileReader::BinaryFileReader(io::IReadFile* pFile,
                                   bool closeWhenDone)
	: file(pFile), autoClose(closeWhenDone)
{
}

BinaryFileReader::~BinaryFileReader()
{
	if(autoClose && file)
	{
		file->drop();
		file = 0;

	}
}

s32 BinaryFileReader::readBuffer(void* buffer, s32 len)
{
	return file->read(buffer,len);

}

s32 BinaryFileReader::readLong()
{
	s32 ret = 0;
	readBuffer(&ret,sizeof(ret));
	return ret;
}

f32 BinaryFileReader::readFloat()
{
	f32 ret = 0;
	readBuffer(&ret,sizeof(ret));
	return ret;
}

u8 BinaryFileReader::readByte()
{
	u8 ret = 0;
	readBuffer(&ret,sizeof(ret));
	return ret;
}

core::stringc BinaryFileReader::readString()
{
	core::stringc str = "";
	c8 c = (c8)readByte();
	while(c != 0)
	{
		str += c;
		c = (c8)readByte();
	}
	return str;
}

void BinaryFileReader::readVec3f(core::vector3df* v)
{
	v->X = readFloat();
	v->Y = readFloat();
	v->Z = readFloat();
}

void BinaryFileReader::readVec2f(core::vector2df* v)
{
	v->X = readFloat();
	v->Y = readFloat();
}

void BinaryFileReader::readColorRGB(color_rgb_t* color)
{
	readBuffer(color,sizeof(color_rgb_t));
}

} // end namespace
} // end namespace

